﻿-------------------------------------------------------------------------------
-- Addon - Buffs - Init
-------------------------------------------------------------------------------
-- Version
local MAJOR = "Avion2_Buffs"
local MINOR = tonumber(("@project-revision@"):match("%d+")) or 0

-- Dependencies
if not Avion2Core 				then error(MAJOR .. " requires Avion2.") end
if not LibStub("AceEvent-3.0")	then error(MAJOR .. " requires AceEvent-3.0.") end

-- Create
Avion2_Buffs 		= Avion2Core:NewModule(MAJOR, "AceEvent-3.0")
Avion2_Buffs.major 	= MAJOR
Avion2_Buffs.minor 	= MINOR


-------------------------------------------------------------------------------
-- Addon - Buffs - Variables
-------------------------------------------------------------------------------
-- Translation
local L 	= LibStub("AceLocale-3.0"):GetLocale(MAJOR)
local coreL	= LibStub("AceLocale-3.0"):GetLocale(Avion2Core.major)

-- Locals
local CombatLogEvent, updateGUIDs


-------------------------------------------------------------------------------
-- Addon - Buffs - Register with core
-------------------------------------------------------------------------------
-- Register SavedVariables
function Avion2_Buffs:RegisterModuleDB()
	
end


-- (Un)register (un)needed events
function Avion2_Buffs:UpdateEventRegistration()
	self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED", CombatLogEvent)
	--RAID_ROSTER_UPDATE
end


-- Update animation (convert animationFunc: string name to function pointer)
function Avion2_Buffs:LoadAnimationTables()
	
end


-------------------------------------------------------------------------------
-- Addon - Buffs - Options
-------------------------------------------------------------------------------
-- Fetch animationTable from animationID (as suplied by eg. Menu_Reuqest)
function Avion2_Buffs:GetAnimationTable(animationID)
	
end


-- Build Optionstable
function Avion2_Buffs:UpdateOptionTable(args)
	
end


--[[
Variablen - Design:
	Für Buff/Debuff nach Namen sorieren (Ein Name kann mehrere Triffer haben! -> Table)
	Typ über UnitAura testen
]]--


-------------------------------------------------------------------------------
-- Addon - Buffs - Events
-------------------------------------------------------------------------------


--[[
-- DB
db = {
	byType = { 
		["Magic"]			= {
			-- animationList
			raid			= {},
			-- animationList
			player			= {},
			name			= {
				-- animationList
				["NAME"]	= {},
				...
			},
		},
		...
		cancelAnimation		= 0,
	},
	aura = {
		[ID] = {
			spell				= "NAME"	-- NAME or ID of spell,
			stacks				= 1,
			auraType			= "BUFF",
			invert				= false,
			onFocus 			= true,
			onTarget 			= true,
			onPlayer 			= true,
			onRaid				= true,
			onName				= {
				["NAME"]		= true,
				...
			},
			animationList		= {},
			cancelAnimation		= 0,
		},
		...
	},
	raid = {
		[ID] = {
			spell				= "NAME"	-- NAME or ID of spell,
			stacks				= 1,
			auraType			= "BUFF",
			invert				= false,
			animationList		= {},
			cancelAnimation		= 0,
		},
		...
	},
}

-- CACHE
isActive = {
	byType = {
		["Magic"]			= {
			raid 			= {
				[GUID] 		= true,
				...
			},
			player			= true,
			name			= {
				["NAME"] 	= true,
				...
			},
		},
		...
	},
	aura = {
		[key] = true,
		...
	},
}

-- Conert
toKey = {
	aura = {
		-- Für alle DB Einträge anlegen
		[spellId] = {
			key,
			...
		},
		...
	},
	raid = {
		-- Für alle DB Einträge anlegen
		[spellId] = {
			key,
			...
		},
		...
	},
}

-- Size of raid
]]--
	

-- This will torch GUID from cache if someone leaves raid
-- Needs to scan whole raid though
updateGUIDs = function()
	-- Locals
	local byType 	= Avion2_Buffs.isActive.byType
	local byRaid 	= Avion2_Buffs.isActive.raidAura
	local guids		= {}
	local guid
	
	-- Scan all raidmembers and check their GUIDs
	for i = 1, 40 do
		-- Current raidmember GUID
		guid = UnitGUID(string:format("raid%d", i)
		
		-- Quite loop if all raidmember done
		if (guid) then
			guids[guid] = true
		else
			break
		end
	end
	
	-- Test if cache GUIDs are still in raid (remove from all lists)
	for guid, _ in pairs(byType["Magic"].raid) do
		-- Remove from cache
		if (not guids[guid]) then
			byType["Magic"].raid[guid] = nil
		end
	end
	for guid, _ in pairs(byType["Disease"].raid) do
		-- Remove from cache
		if (not guids[guid]) then
			byType["Disease"].raid[guid] = nil
		end
	end
	for guid, _ in pairs(byType["Poison"].raid) do
		-- Remove from cache
		if (not guids[guid]) then
			byType["Poison"].raid[guid] = nil
		end
	end
	for guid _ in pairs(byType["Curse"].raid) do
		-- Remove from cache
		if (not guids[guid]) then
			byType["Curse"].raid[guid] = nil
		end
	end
end


-- Scan unit if it has a certain debuff type applied
-- Return via result (as table with ["TYPE"] = STATE)
local scanDebuffType(unit, result)
	-- Locals and defaults
	local debuffName, debuffType
	result["Magic"] 	= false
	result["Disease"] 	= false
	result["Poison"] 	= false
	result["Curse"] 	= false

	-- Scan if player doesn't have any more debuffs applied
	for buffIndex = 1, 40 do
		-- Scan für type of debuff
		debuffName, _, _, _, debuffType = UnitAura(unit, buffIndex, nil, "HARMFUL")
		
		-- All debuffs scanned
		if (not debuffName) then
			return
		end
		
		-- Debuff type found?
		if (debuffType) then
			result[debuffType] 	= true
		end
	end
end
	

-- Scan if someone has a debuff
CombatLogEvent_TypeApplied = function(destGUID, destName, isRaid, isPlayer, debuffType)
	-- Locals for debuff type
	local db 		= Avion2_Buffs.db.profile.byType
	local isActive 	= Avion2_Buffs.isActive.byType
	
	-- Is on player and was not allready active
	if (isPlayer and db[debuffType].player) then
		-- Only trigger once per apply
		if (not isActive[debuffType].player) then
			-- Set active
			isActive[debuffType].player = true
			
			-- Trigger
			Avion2Core:TriggerAnimationList("Avion2_Debufftyp_" .. debuffType .. "_Player", db[debuffType].player)
		end
	
	-- Is someone with given name and was not allready active
	elseif (db[debuffType].name[destName]) then
		-- Only trigger once per apply
		if (not isActive[debuffType].name[destName]) then
			-- Set active
			isActive[debuffType].name[destName] = true
			
			-- Trigger
			Avion2Core:TriggerAnimationList("Avion2_Debufftyp_" .. debuffType .. "_Name_" .. destName, db[debuffType].name[destName])
		end
		
	-- Is on raidmember and was not allready active
	elseif (isRaid and db[debuffType].raid) then
		-- Only trigger once per apply
		if (#(isActive[debuffType].raid) == 0) then
			-- Trigger
			Avion2Core:TriggerAnimationList("Avion2_Debufftyp_" .. debuffType .. "_Raid", db[debuffType].raid)
		end
	end
	
	-- Set active (keep raid updated)
	if (isRaid and db[debuffType].raid) then
		isActive[debuffType].raid[destGUID] = true
	end 
end


-- Scan if aura matches a trigger
CombatLogEvent_AuraApplied = function(destName, spellId, spellName, auraType, isRaid, isPlayer, isTarget, isFocus, stacks)
	-- Locals
	local isActive 	= Avion2_Buffs.isActive.aura
	local db 		= Avion2_Buffs.db.profile.aura
	
	-- Test all triggers
	for _, key in pairs(self.toKey.aura[spellId]) do
		-- Trigger animation if feasable
		if (
			auraType 		== db[key].auraType 	and 
			(
				spellName 	== db[key].spell 		or 
				spellId 	== db[key].spell
			)										and 
			stacks 			>= db[key].stacks 		and 
			not isActive[key] 						and 
			(
				isPlayer 	and db[key].onPlayer 	or 
				isFocus 	and db[key].onFocus 	or
				db[key].onName[destName] 			or 
				isRaid 		db[key].onRaid			or
				isTarget 	and db[key].onTarget
			)
		) then
			-- Set active
			isActive[key] = true

			-- Trigger
			Avion2Core:TriggerAnimationList(string:format("Avion2_Buff_%d", key), db[key].animationList)
		end
	end
end


-- Scan if aura is now on all players
CombatLogEvent_RaidAuraApplied = function(spellId, spellName, filter)
	-- Locals
	local isActive 	= Avion2_Buffs.isActive.raid
	local db 		= Avion2_Buffs.db.profile.raid
	local name, valid, stacks
	
	-- Test all triggers
	for _, key in pairs(self.toKey.raid[spellId]) do
		-- Valid?
		valid = true
		
		-- Scan all raidmember
		for i = 1, 40 do
			-- Scan raidmember
			name, _, _, stacks = UnitAura(string:format("raid%d", i), spellName, nil, filter)

			-- Done scanning
			if (not UnitInRaid(string:format("raid%d", i))) then
				break;
			end
			
			-- Valid -> Update
			if (not name or stacks < db[key].stacks) then
				valid = false
			end
		end
		
		-- Valid -> trigger
		if (valid) then
			Avion2Core:TriggerAnimationList(string:format("Avion2_RaidBuff_%d", key), db[key].animationList)
		end
	end
end


-- Aura was applied on destGUID
CombatLogEvent_Applied = function(logEvent, destGUID, destName, spellId, spellName, auraType, filter, isRaid, isPlayer, isTarget, isFocus)
	-- Trigger animation for debuff type applied to ...
	if (auraType == "DEBUFF" and logEvent:sub(-13) == "_AURA_APPLIED") then
		CombatLogEvent_TypeApplied(destGUID, destName, isRaid, isPlayer, stacks)
	end

	-- Anything too test against?
	if (self.toKey.aura[spellId]) then
		CombatLogEvent_AuraApplied(destName, spellId, spellName, auraType, isRaid, isPlayer, isTarget, isFocus, debuffType)
	end
	
	-- Anything too test against?
	if (self.toKey.raid[spellId]) then
		CombatLogEvent_RaidAuraApplied(spellId, spellName, filter)
	end
end


-- Scan if debuff type was removed
CombatLogEvent_TypeRemoved = function(destGUID, destName, isRaid, isPlayer)
	-- Locals for debuff type
	local db		= Avion2_Buffs.db.profile.byType
	local wasActive	= Avion2_Buffs.isActive.byType
	local isActive 	= {}
	
	-- Only scan player when it's a event about him
	if (isPlayer and (db["Magic"].player or db["Disease"].player or db["Poison"].player or db["Curse"].player)) then
		-- Scan
		scanDebuffType("player", isActive)
		
		-- Check all types of debuffs
		for key, value in pairs(isActive) do
			-- No debuff of this type but there WAS
			if (not value and wasActive[key].player) then
				-- Set inactive
				wasActive[key].player = false
			
				-- Stop
				Avion2Core:StopAnimationList("Avion2_Debufftyp_" .. key .. "_Player", db[key].player, (db.cancelAnimation == 1))
			end
		end
		
	-- Only scan name when it's a event about him
	elseif (db["Magic"].name[destName] or db["Disease"].name[destName] or db["Poison"].name[destName] or db["Curse"].name[destName]) then
		-- Scan
		scanDebuffType(destName, isActive)
		
		-- Check all types of debuffs
		for key, value in pairs(isActive) do
			-- No debuff of this type but there WAS
			if (not value and wasActive[key].name[destName]) then
				-- Set inactive
				wasActive[key].name[destName] = false
			
				-- Stop
				Avion2Core:StopAnimationList("Avion2_Debufftyp_" .. key]. .. "_Name_" .. destName, db[key].].name[destName], (db.cancelAnimation == 1))
			end
		end
		
	-- Only scan raid when it's a event about him
	elseif (isRaid and (db["Magic"].raid or db["Disease"].raid or db["Poison"].raid or db["Curse"].raid)) then
		-- Scan
		scanDebuffType(destName, isActive)
		
		-- Check all types of debuffs
		for key, value in pairs(isActive) do	
			-- No debuff of this type ...
			if (not value) then
				-- Remove from list
				wasActive[key].raid[destGUID] = nil
				
				-- Either tracking single raid member or all
				if (db[key].onRaid == 1 or #(wasActive[key].raid) < GetNumRaidMembers() ) then
					Avion2Core:StopAnimationList("Avion2_Debufftyp_" .. debuffType .. "_Raid", db[debuffType].raid, (db.cancelAnimation == 1))
				end
			end
		end
	end
end


-- Scan if aura doesn't fit requirements anymore
CombatLogEvent_AuraRemoved = function(spellId, stacks)
	-- Locals
	local isActive 	= Avion2_Buffs.isActive.aura
	local db 		= Avion2_Buffs.db.profile.aura

	-- Test all available spells aura active
	for _, key in pairs(self.toKey.aura[spellId]) do
		-- Cancel setting used and was 
		if (isActive[key] and stacks < db[key].stacks) then
			-- Set inactive
			isActivekey[key] = nil
		
			-- Cancel
			if (db[key].cancelAnimation ~= 0) then
				Avion2Core:StopAnimationList(string:format("Avion2_Buff_%d", key), db[key].animationList, (db[key].cancelAnimation == 1))
			end
		end
	end
end


-- Scan if aura doesn't fit requirements on any raidmember anymore
CombatLogEvent_RaidAuraRemoved = function(spellId, stacks)
	-- Locals
	local isActive 	= Avion2_Buffs.isActive.raid
	local db 		= Avion2_Buffs.db.profile.raid

	-- Test all triggers
	for _, key in pairs(self.toKey.raid[spellId]) do
		-- Stop
		if (db[key].cancelAnimation ~= 0 and stacks < db[key].stacks) then
			Avion2Core:StopAnimationList(string:format("Avion2_RaidBuff_%d", key), db[key].animationList, (db[key].cancelAnimation == 1))
		end
	end
end

	
-- Aura was removed from destGUID
CombatLogEvent_Removed = function(logEvent, destGUID, destName, auraType, stacks)
	-- Stop animation for debuff type (if cancelAnimation is set)
	if (Avion2_Buffs.db.profile.byType.cancelAnimation ~= 0 and logEvent:sub(-13) == "_AURA_REMOVED" and auraType == "DEBUFF") then
		CombatLogEvent_TypeRemoved(destGUID, destName, isRaid, isPlayer)
	end
	
	-- Information available for this spell?
	if (self.toKey.aura[spellId]) then
		CombatLogEvent_AuraRemoved(spellId, stacks)
	end
	
	-- Anything too test against?
	if (self.toKey.raid[spellId] and (logEvent:sub(-13) == "_AURA_REMOVED" or logEvent:sub(-13) == "_AURA_BROKEN") then
		CombatLogEvent_RaidAuraRemoved(spellId, stacks)
	end
end


-- Combat log event triggered
CombatLogEvent = function(...)
-- !!! INVERT

	-- Watch only Spellevents
	-- ... =
	--  	1) COMBAT_LOG_EVENT_UNFILTERED
	--		2) timestamp
	--		3) event
	--		4) sourceGUID
	--		5) sourceName
	--		6) sourceFlags
	--		7) destGUID
	--		8) destName
	--		9) destFlags
	--		10)-20)...MISC
	local logEvent = select(3, ...)
	
	-- Listen to
	--  Prefix:
	--		- SPELL*
	--	Suffix:
	--		- _AURA_APPLIED*
	--		- _AURA_REMOVED*
	--		- _AURA_BROKEN*
	if (logEvent:sub(1,5) == "SPELL" and (logEvent:find("_AURA_APPLIED") or logEvent:find("_AURA_REMOVED") or logEvent:sub(-6) == "BROKEN")) then
		-- Default arguments +
		-- ...MISC =
		--		11) spellID
		--		11) spellName
		--		12) spellSchool
		--		13) auraType
		--		(14) amount)
		local destGUID, destName, _, spellId, spellName, _, auraType = select(7, ...)

		-- Applied to whom?
		-- Note: isPlayer > byName > isTarget > isFocus > isRaid
		local isRaid 	= UnitInRaid(destName)
		local isPlayer 	= (UnitGUID("player") == destGUID)
		local isTarget	= (UnitGUID("target") == destGUID)
		local isFocus	= (UnitGUID("focus")  == destGUID)
				
		-- Buff or Debuff
		local filter
		if (auraType == "BUFF") then
			filter = "HELPFUL"
		else
			filter = "HARMFUL"
		end
		
		-- Query stacks and type of (debuff)
		local stacks, debuffType
		
		-- Player and raid can be queried directly (via name)
		if (isPlayer) then
			stacks, debuffType = select(3, UnitAura("player", spellName, nil, filter))
		elseif (isRaid) then
			stacks, debuffType = select(3, UnitAura(destName, spellName, nil, filter))
		-- Scan target
		elseif (isTarget) then
			stacks, debuffType = select(3, UnitAura("target", spellName, nil, filter))
		-- Scan focus
		elseif (isFocus) then
			stacks, debuffType = select(3, UnitAura("focus", spellName, nil, filter))
		-- Ignore other cases (because I can't query anything for them)!
		else
			return
		end
	
		-- Aura first applied or stack increased
		if (logEvent:find("_AURA_APPLIED")) then
			CombatLogEvent_Applied(logEvent, destGUID, destName, spellId, spellName, auraType, filter, isRaid, isPlayer, isTarget, isFocus, stacks, debuffType)
		-- Aura removed or stack decreased
		else
			CombatLogEvent_Removed(logEvent, destGUID, destName, auraType, stacks)
		end
	end
end